﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace ContextualStateless
{
    public partial class StateMachine< TState, TTrigger, TContext >
    {
        /// <summary>
        /// Represent state machine's configured state or substate.
        /// </summary>
        internal protected class StateRepresentation
        {
            #region Attributes

            /// <summary>
            /// Represented state.
            /// </summary>
            public TState UnderlyingState { get; protected set; }

            /// <summary>
            /// Substate's owner (container) state.
            /// State is a substate of this composite state. null if state is not substate of composite state.
            /// </summary>
            public StateRepresentation SuperState { get; set; }

            /// <summary>
            /// Composite state's substates.
            /// </summary>
            protected readonly ICollection< StateRepresentation > m_subStates = new List<StateRepresentation>();

            /// <summary>
            /// Dictionary of trigger conditions (TriggerBehaviour) for particular trigger types.
            /// </summary>
            protected readonly IDictionary<TTrigger, ICollection<AbstractTriggerRepresentation>> m_triggerBehaviours = new Dictionary<TTrigger, ICollection<AbstractTriggerRepresentation>>();

            protected readonly ICollection<Action<StateTransition, object[]>> m_entryActions = new List<Action<StateTransition, object[]>>();
            protected readonly ICollection<Action<StateTransition>> m_exitActions = new List<Action<StateTransition>>();

            #endregion

            public StateRepresentation(TState state)
            {
                UnderlyingState = state;
            }

            // -----------------------------------------------------------------------------------------------------------

            public bool CanHandle( TTrigger trigger, TContext context )
            {
                AbstractTriggerRepresentation _unused;

                return TryFindHandler( trigger, context, out _unused );
            }

            public virtual bool TryFindHandler( TTrigger trigger, TContext context, out AbstractTriggerRepresentation handler )
            {
                return TryFindLocalHandler( trigger, context, out handler ) || 
                    SuperState != null && SuperState.TryFindHandler( trigger, context, out handler );
            }
            
            protected bool TryFindLocalHandler( TTrigger trigger, TContext context, out AbstractTriggerRepresentation handler )
            {
                ICollection< AbstractTriggerRepresentation > _triggerGuards;    // all guards for the trigger in the current state

                if ( !m_triggerBehaviours.TryGetValue( trigger, out _triggerGuards ) )
                {
                    handler = null;
                    return false;
                }

                //Contract.Assert( _triggerGuards != null, "_triggerGuards" );

                var _satisfiedGuards = _triggerGuards.Where( x => x.IsGuardConditionMet( context ) );

                // more than 1 guard is satisfied
                if ( _satisfiedGuards.Count() > 1 )
                    throw new InvalidOperationException( string.Format( StateRepresentationResources.MultipleTransitionsPermitted, trigger, UnderlyingState ) );

                handler = _satisfiedGuards.FirstOrDefault();

                return handler != null;                
            }

            public void AddEntryAction(TTrigger trigger, Action<StateTransition, object[]> action)
            {
                Enforce.ArgumentNotNull(action, "action");
                //Contract.Requires( action != null, "action" );

                m_entryActions.Add( ( t, args ) =>
                    {
                        if (t.Trigger.Equals(trigger))
                            action(t, args);
                    });
            }

            public void AddEntryAction(Action<StateTransition, object[]> action)
            {
                m_entryActions.Add(Enforce.ArgumentNotNull(action, "action"));
            }

            public void AddExitAction(Action<StateTransition> action)
            {
                m_exitActions.Add(Enforce.ArgumentNotNull(action, "action"));
            }

            /// <summary>
            /// Initiates execution of entry actions when entering represented (underlying) state.
            /// </summary>
            /// <param name="transition">transition to underlying state</param>
            /// <param name="entryArgs"></param>
            protected internal void Enter( StateTransition transition, params object[] entryArgs )
            {
                Enforce.ArgumentNotNull( transition, "transtion" );
//                Contract.Requires( transition != null, "transition" );
//                Contract.Requires( UnderlyingState.Equals( transition.Destination ), "incoming transition destination" );

                // re-entering transition or transition from own (internal) substate
                if ( transition.IsReentry || Includes( transition.Source ) )
                    ExecuteEntryActions( transition, entryArgs );
                else 
                    // transition from external state
                    if ( !Includes( transition.Source ) )
                    {
                        if ( SuperState != null )
                            SuperState.Enter( transition, entryArgs );

                        ExecuteEntryActions( transition, entryArgs );
                    }
            }

            /// <summary>
            /// Initiates execution of exit actions when leaving represented (underlying) state.
            /// </summary>
            /// <param name="transition">transition from underlying state</param>
            protected internal void Exit( StateTransition transition )
            {
                Enforce.ArgumentNotNull(transition, "transtion");
//                Contract.Requires( transition != null, "transition" );
//                Contract.Requires( UnderlyingState.Equals( transition.Source ), "outcoming transition source" );

                // re-entering transition or transition to own (internal) substate
                if ( transition.IsReentry || Includes( transition.Destination ) )
                    ExecuteExitActions( transition );
                else 
                    // transition to external state
                    if ( !Includes( transition.Destination ) )
                    {
                        ExecuteExitActions(transition);

                        if ( SuperState != null )
                            SuperState.Exit(transition);
                    }
            }

            /// <summary>
            /// Executes underlying state entry actions.
            /// </summary>
            /// <param name="transition">incoming transition to underlying state</param>
            /// <param name="entryArgs"></param>
            protected void ExecuteEntryActions( StateTransition transition, object[] entryArgs )
            {
                Enforce.ArgumentNotNull(transition, "transtion");
                Enforce.ArgumentNotNull(entryArgs, "entryArgs");
//                Contract.Requires( transition != null, "transtion" );
//                Contract.Requires( entryArgs != null, "entryArgs" );

                foreach (var action in m_entryActions )
                    action( transition, entryArgs );
            }

            /// <summary>
            /// Executes underlying state exit actions.
            /// </summary>
            /// <param name="transition">outcoming transition from underlying state</param>
            protected void ExecuteExitActions(StateTransition transition)
            {
                Enforce.ArgumentNotNull(transition, "transtion");
                //Contract.Requires( transition != null, "transition" );

                foreach (var action in m_exitActions)
                    action(transition);
            }

            public void AddTriggerBehaviour( AbstractTriggerRepresentation triggerRepresentation )
            {
                Enforce.ArgumentNotNull( triggerRepresentation, "triggerRepresentation" );
                //Contract.Requires( triggerRepresentation != null, "triggerRepresentation" );

                ICollection< AbstractTriggerRepresentation > _triggerGuards;
                if ( !m_triggerBehaviours.TryGetValue( triggerRepresentation.Trigger, out _triggerGuards ) )
                {
                    _triggerGuards = new List< AbstractTriggerRepresentation >();
                    m_triggerBehaviours.Add(triggerRepresentation.Trigger, _triggerGuards);
                }

                _triggerGuards.Add( triggerRepresentation );
            }

            public void AddSubstate(StateRepresentation substate)
            {
                Enforce.ArgumentNotNull(substate, "substate");
                //Contract.Requires( substate != null, "substate" );

                m_subStates.Add(substate);
            }

            /// <summary>
            /// Returns true if state is underlying state or it's substate.
            /// </summary>
            /// <param name="state">checked state</param>
            /// <returns>true if state is underlying state or it's substate and false otherwise</returns>
            public bool Includes( TState state )
            {
                return UnderlyingState.Equals( state ) || m_subStates.Any( subState => subState.Includes( state ) );
            }

            public bool IsIncludedIn(TState state)
            {
                return UnderlyingState.Equals( state ) || SuperState != null && SuperState.IsIncludedIn( state );
            }

            /// <summary>
            /// Returns collection of permitted triggers for the context.
            /// </summary>
            /// <param name="context">context</param>
            /// <returns>collection of permitted triggers</returns>
            public virtual IEnumerable< TTrigger > PermittedTriggers( TContext context )
            {
                var _permittedTriggers = m_triggerBehaviours.Where( t => t.Value.Any( a => a.IsGuardConditionMet( context ) ) ).Select( t => t.Key );

                return SuperState != null ? _permittedTriggers.Union( SuperState.PermittedTriggers( context ) ) : _permittedTriggers;

                //TODO: check if doesn't break anything
//                if ( SuperState != null)
//                    _permittedTriggers = _permittedTriggers.Union( SuperState.PermittedTriggers( context ) );
//
//                return _permittedTriggers.ToArray();
            }
        }
    }
}
